﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace GBricks.Collections
{
  [Serializable]
  [DebuggerDisplay("Count = {Count}"), DebuggerTypeProxy("System.Collections.Generic.Mscorlib_DictionaryDebugView`2, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  public class ItemDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
  {
    public ItemDictionary() : this(new Dictionary<TKey, TValue>()) { }
    public ItemDictionary(IEqualityComparer<TKey> comparer) : this(new Dictionary<TKey, TValue>(comparer)) { }
    public ItemDictionary(int capacity, IEqualityComparer<TKey> comparer = null) : this(new Dictionary<TKey, TValue>(capacity, comparer)) { }

    public ItemDictionary(IEnumerable<KeyValuePair<TKey, TValue>> items, IEqualityComparer<TKey> comparer = null) {
      Argument.NotNull(items, "items");
      Dictionary = items.ToDictionary(comparer);
      Contract.Assert(Dictionary.Count == Count);
    }

    protected ItemDictionary(IDictionary<TKey, TValue> dictionary) {
      Argument.NotNull(dictionary, "dictionary");
      Dictionary = dictionary;
      Contract.Assert(Dictionary.Count == Count);
    }

    [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
    protected IDictionary<TKey, TValue> Dictionary { get; private set; }

    protected virtual ICollection<TKey> KeyItems {
      [DebuggerStepThrough]
      get {
        Contract.Ensures(Contract.Result<ICollection<TKey>>() != null);
        Contract.Ensures(Contract.Result<ICollection<TKey>>().Count == Count);
        return Dictionary.Keys;
      }
    }

    protected virtual ICollection<TValue> ValueItems {
      [DebuggerStepThrough]
      get {
        Contract.Ensures(Contract.Result<ICollection<TValue>>() != null);
        Contract.Ensures(Contract.Result<ICollection<TValue>>().Count == Count);
        return Dictionary.Values;
      }
    }

    public void Add(IEnumerable<KeyValuePair<TKey, TValue>> items) {
      Argument.NotNull(items, "items");

      foreach(var item in items) {
        Add(item);
      }//for
    }

    [ContractInvariantMethod]
    private void Invariant() {
      Contract.Invariant(Dictionary != null);
      Contract.Invariant(Dictionary.Count == Count);
      Contract.Invariant(Dictionary.Keys.Count == Count);
      Contract.Invariant(Dictionary.Values.Count == Count);
    }

    protected virtual void CopyItemsTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(arrayIndex >= 0, "arrayIndex");
      Argument.OutOfRange(arrayIndex <= array.Length - Count, "arrayIndex");
      Dictionary.CopyTo(array, arrayIndex);
      Contract.Assert(Dictionary.Count == Count);
    }

    protected virtual void AddItem(TKey key, TValue value) {
      Dictionary.Add(key, value);
      Contract.Assert(Dictionary.Count == Count);
    }

    protected virtual void SetItem(TKey key, TValue value) {
      Dictionary[key] = value;
    }

    protected virtual bool RemoveItem(TKey key) {
      var removed = Dictionary.Remove(key);
      Contract.Assert(Dictionary.Count == Count);
      return removed;
    }

    protected virtual void ClearItems() {
      Contract.Ensures(Count == 0);
      Dictionary.Clear();
      Contract.Assert(Dictionary.Count == Count);
    }

    [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = Justifications.ByDesign)]
    [Pure]
    protected virtual IEnumerator<KeyValuePair<TKey, TValue>> GetItemEnumerator() {
      Contract.Ensures(Contract.Result<IEnumerator<KeyValuePair<TKey, TValue>>>() != null);
      return Dictionary.GetEnumerator();
    }

    #region IDictionary<TKey,TValue> Members

    public ICollection<TKey> Keys {
      [DebuggerStepThrough]
      get {
        Contract.Ensures(Contract.Result<ICollection<TKey>>() != null);
        return KeyItems;
      }
    }

    public ICollection<TValue> Values {
      [DebuggerStepThrough]
      get {
        Contract.Ensures(Contract.Result<ICollection<TValue>>() != null);
        return ValueItems;
      }
    }

    public TValue this[TKey key] {
      [DebuggerStepThrough]
      get { return Dictionary[key]; }
      set { SetItem(key, value); }
    }

    [Pure]
    public bool ContainsKey(TKey key) {
      Contract.Ensures(!Contract.Result<bool>() || Count > 0);
      return Dictionary.ContainsKey(key);
    }

    public bool TryGetValue(TKey key, out TValue value) {
      Contract.Ensures(Contract.Result<bool>() == ContainsKey(key));
      Contract.Assume(Dictionary.ContainsKey(key) == ContainsKey(key));
      return Dictionary.TryGetValue(key, out value);
    }

    public void Add(TKey key, TValue value) {
      AddItem(key, value);
    }

    public bool Remove(TKey key) {
      return RemoveItem(key);
    }

    #endregion IDictionary<TKey,TValue> Members

    #region IReadOnlyDictionary<TKey,TValue> Members

    IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys {
      [DebuggerStepThrough]
      get { return Keys; }
    }

    IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values {
      [DebuggerStepThrough]
      get { return Values; }
    }

    #endregion IReadOnlyDictionary<TKey,TValue> Members

    #region ICollection<KeyValuePair<TKey, TValue>> Members

    public int Count {
      [DebuggerStepThrough]
      get {
        Contract.Ensures(Contract.Result<int>() >= 0);
        return Dictionary.Count;
      }
    }

    bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
      [DebuggerStepThrough]
      get { return false; }
    }

    [SuppressMessage("Microsoft.Contracts", "Ensures", Justification = Justifications.CodeContractsIssue)]
    public void Add(KeyValuePair<TKey, TValue> item) {
      Contract.Ensures(Count >= Contract.OldValue(Count));
      Add(item.Key, item.Value);
    }

    public void Clear() {
      Contract.Ensures(Count == 0);
      ClearItems();
    }

    [Pure]
    public bool Contains(KeyValuePair<TKey, TValue> item) {
      Contract.Ensures(!Contract.Result<bool>() || Count > 0);
      TValue value;
      return TryGetValue(item.Key, out value) && EqualityComparer<TValue>.Default.Equals(item.Value, value);
    }

    [Pure]
    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(arrayIndex >= 0, "arrayIndex");
      Argument.OutOfRange(arrayIndex <= array.Length - Count, "arrayIndex");
      CopyItemsTo(array, arrayIndex);
      Contract.Assert(Dictionary.Count == Count);
    }

    [SuppressMessage("Microsoft.Contracts", "Ensures", Justification = Justifications.CodeContractsIssue)]
    public bool Remove(KeyValuePair<TKey, TValue> item) {
      Contract.Ensures(Count <= Contract.OldValue(Count));
      Contract.Ensures(!Contract.Result<bool>() || Count >= Contract.OldValue(Count) - 1);

      TValue value;
      return TryGetValue(item.Key, out value) && EqualityComparer<TValue>.Default.Equals(item.Value, value)
        && Remove(item.Key);
    }

    #endregion ICollection<KeyValuePair<TKey, TValue>> Members

    #region IEnumerable<KeyValuePair<TKey, TValue>> Members

    [Pure]
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
      Contract.Ensures(Contract.Result<IEnumerator<KeyValuePair<TKey, TValue>>>() != null);
      return GetItemEnumerator();
    }

    #endregion IEnumerable<KeyValuePair<TKey, TValue>> Members

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator() {
      Contract.Ensures(Contract.Result<IEnumerator>() != null);
      foreach(var item in this) {
        yield return new DictionaryEntry(item.Key, item.Value);
      }//for
    }

    #endregion IEnumerable Members
  }
}
